package plotagem;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.text.Normalizer;
import java.util.Arrays;
import java.util.List;
import java.util.SortedMap;
import java.util.TimeZone;
import java.util.Map;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.mail.internet.*;

import org.json.JSONArray;
import org.json.JSONObject;

import com.google.appengine.api.blobstore.BlobInfoFactory;
import com.google.appengine.api.blobstore.BlobInfo;
import com.google.appengine.api.blobstore.BlobKey;
import com.google.appengine.api.blobstore.BlobstoreService;
import com.google.appengine.api.blobstore.BlobstoreServiceFactory;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.files.AppEngineFile;
import com.google.appengine.api.files.FileReadChannel;
import com.google.appengine.api.files.FileService;
import com.google.appengine.api.files.FileServiceFactory;
import com.google.appengine.api.files.FileWriteChannel;
import com.google.appengine.api.users.User;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;
/**
 * 
 * 
 * @author André Luis Soares de Matos (alsmatos77@gmail.com)
 * @version 2013-03-20
 *	O objetivo dessa Aplicação é realizar o upload de um arquivo de texto com dados separados por tabulações de acordo com um padrão pré-estabelecido e tratar os dados 
 *contidos no arquivo para armazenamento e criação dos marcadores georreferenciados, o padrão segue abaixo:
 *		1) Coluna 1 deve conter os títulos dos locais;
 *		2) Coluna 2 deve conter a descrição dos locais;
 *		3) Coluna 3 deve conter o endereço que será procurado;
 *		4) Os dados das demais colunas são livres;
 *		5) A primeira linha deve conter os títulos das colunas;
 *		6) A codificação do arquivo deve ser UTF-8;
 *		7) O limite é de 600 linha e 100Kb, por motivo de desempenho.
 *	Após o upload, a Aplicação lê os dados e busca informações do georeferenciamento dos endereços e armazena essas informações de georeferenciamento em cache,
 *para posterior pesquisa recuperar os dados de um mesmo endereço localmente.
 *	Os dados encontrados no Google Geocode e os dados fornecidos pelo usuário são armazenados na forma de um SortedMap para cada usuário, de um blob 
 *para os erros e outro blob para o texto enviado.
 *	Para visualizar os dados, o usuário conta com o Plugin do Google Maps, com um dhtmlxGrid para os endereços encontrados e outro para os endereços não localizados.
 *	Se o resultado da busca não for satisfatório, o usuário poderá ajustar os marcadores no mapa manualmente e salvar as alterações e, a qualquer momento, solicitar a elaboração
 *de um arquivo .kml para visualização no Google Earth.
 *	O usuário poderá fazer o download do arquivo original, do arquivo de erros e do kml e, também, apagar todas as informações e arquivos.
 *
 */
public class PlotagemServlet extends HttpServlet  {
	/**
	 * 
	 */
	private static final long serialVersionUID = 4651728137146899869L;
	private BlobstoreService blobstoreService = BlobstoreServiceFactory.getBlobstoreService();
	FileService fileService = FileServiceFactory.getFileService();
	private UserService userService = UserServiceFactory.getUserService();
	private User user = userService.getCurrentUser();
	public void doPost(HttpServletRequest req, HttpServletResponse res)
			 throws IOException, ServletException {
// É necessário trabalhar com usuário autenticado para se poder armazenar os arquivos		
	    if (user != null) {
//Ajustando o fuso horário para o Brasil	    	
	    	TimeZone.setDefault(TimeZone.getTimeZone("GMT-3:00"));
	    	try{
//Recebendo o arquivo que foi enviado:
	    		Map<String, List<BlobKey>> blobs = blobstoreService.getUploads(req);
			    List<BlobKey> ListblobKey = blobs.get("arquivo");
			    BlobKey blobKey = ListblobKey.get(0);
//Opção pelo BlobInfoFactory() em vez do BlobstoreService para trazer as informações do arquivo:
			    BlobInfo info = new BlobInfoFactory().loadBlobInfo(blobKey);
			    String nomeArquivo[]=info.getFilename().split("\\.");
			    String dataCriacao = Utilidades.dt(info.getCreation());
			    if (blobKey != null) {
//Leitura do txt e criação do arquivo de erros e das informações de plotagem
			    	 AppEngineFile file = fileService.getBlobFile(blobKey);
				     FileReadChannel readChannel = fileService.openReadChannel(file, false);
				     AppEngineFile erros = fileService.createNewBlobFile("text/plain", nomeArquivo[0]+"_erros.txt");
				     FileWriteChannel writeChannel2 = fileService.openWriteChannel(erros, true);
			         PrintWriter out = new PrintWriter(Channels.newWriter(writeChannel2, "UTF8"));
				     BufferedReader reader = new BufferedReader(Channels.newReader(readChannel, "UTF8"),100*1024);
				     String line;
				     String titulos[] = reader.readLine().split("\t");
				     int len = titulos.length;
				     String linhaTitulo = "";
				     String linhaTituloErros = "";
			         out = new PrintWriter(Channels.newWriter(writeChannel2, "UTF8"));
			         for(int i = 0;i<titulos.length;i++){
				    	 if(i!=0){
				    		 out.print("\t");
				    		 linhaTituloErros = linhaTituloErros + ",";
				    	 };
				    	 out.print(titulos[i]);
				    	 linhaTituloErros = linhaTituloErros + titulos[i].replaceAll(",","-");
				     }
			         titulos = Arrays.copyOf(titulos, len+3);
			         titulos[len]="Latitude";
			         titulos[len+1]="Longitude";
			         titulos[len+2]="Endereço encontrado";
			         for(int i = 0;i<titulos.length;i++){
				    	 if(i!=0){
				    		 linhaTitulo = linhaTitulo + ",";
				    	 };
				    	 linhaTitulo = linhaTitulo + titulos[i].replaceAll(",","-");
				     }
			         out.print("\n");
				     out.close();
// enderecosEncontrados é a variavel que armazena na forma de String Json os dados dos locais a serem plotados
				     StringBuffer enderecosEncontrados = new StringBuffer("");
				     StringBuffer enderecosErros = new StringBuffer("");
				     int n=0;//Limitar a leitura em 200 linhas
				     int linhaEnderecosEncontrados=0;
				     int linhaEnderecosErros=0;
				     while ((line = reader.readLine()) != null && n<=600) {
				    	 String datavalue[] = line.split("\t");
				    	 if(line.length()>0){
			    		 //StringEndereco: String do endereco em ASCII e com espaços substituídos por "+"
				    		 String StringEndereco = Utilidades.gerarStringEndereco(datavalue[2]);
					    	 try {
					    		 //StringJason: String de resultado da pesquisa de endereço no formato json 
					    		 String StringJason = Utilidades.solicitarCoordenadas(StringEndereco).toString();
					    		 JSONObject json_obj = new JSONObject(StringJason);
					    		 if(json_obj.getString("status").equals("OK")){
					    			 String endereco = ((JSONObject) json_obj.getJSONArray("results").get(0)).getString("formatted_address");
					    			 JSONObject location = (JSONObject)((JSONObject) ((JSONObject) json_obj.getJSONArray("results").get(0)).get("geometry")).get("location");
					    			 String longitude = location.get("lng").toString();
					    			 String latitude = location.get("lat").toString();
					    			 String dados[] = new String[3];
					    			 dados[0]=latitude;
									 dados[1]=longitude;
									 dados[2]=endereco;
									 if(linhaEnderecosEncontrados>0){
										 enderecosEncontrados.append(",");
										 }
						    		 enderecosEncontrados.append("\n{id:").append(linhaEnderecosEncontrados + 1).append(",data:[\"");
								     for(int i = 0;i<datavalue.length;i++){
								    	 enderecosEncontrados.append(datavalue[i].replaceAll("\"","'")).append("\",\"");
								     }
								     enderecosEncontrados.append(latitude).append("\",\"").append(longitude).append("\",\"").append(endereco).append("\"]}");
								     linhaEnderecosEncontrados++;
		 				    		 }else{
		 				    			String path = erros.getFullPath();
		 				    			Utilidades.escreverArquivo(line+"\n",path);
		 				    			if(linhaEnderecosErros>0){enderecosErros.append(",");}
		 				    			enderecosErros.append("\n{id:").append(n + 1).append(",data:[\"");
		 							     for(int i = 0;i<datavalue.length;i++){
		 							    	 if(i!=0){
		 							    		enderecosErros.append("\",\"");
		 							    	 };
		 							    	enderecosErros.append(datavalue[i].replaceAll("\"","'"));
		 							     }
		 							    enderecosErros.append("\"]}");
		 							   linhaEnderecosErros++;
		 				    		 }
					    	 } catch (Exception e) {
					    		// TODO Auto-generated catch block
									e.printStackTrace();
					    	 }
				    	 }
				    	 n++;
				     }
				     reader.close();
				     String path  = erros.getFullPath();
					 erros = new AppEngineFile(path);
					 writeChannel2.closeFinally();
					 BlobKey blobKeyErros = fileService.getBlobKey(erros);
					 String Usuario = user.toString();
					 //Usando o recurso createKeyString apenas para identificar, de forma única e por meio de chave, cada lista de arquivos
					 String chave = KeyFactory.createKeyString("mapas", nomeArquivo[0]+dataCriacao);
					 String lista[] = new String[10];
					 lista[0] = nomeArquivo[0];
					 lista[1] = dataCriacao;
					 lista[2] = blobKey.getKeyString();
					 lista[3] = blobKeyErros.getKeyString();
					 lista[4] = linhaTitulo;
					 lista[5] = enderecosEncontrados.toString();
					 lista[6] = enderecosErros.toString();
					 lista[7] = linhaTituloErros;
					 Utilidades.adicionarNoMapa(Usuario, chave, lista);
					 RequestDispatcher rd = null;
					 req.setAttribute("titulos", lista[4] );
					 req.setAttribute("enderecosEncontrados", lista[5] );
					 req.setAttribute("enderecosErros", lista[6] );
					 req.setAttribute("titulosErros", lista[7] );
					 req.setAttribute("chave", chave);
					 rd=req.getRequestDispatcher("/plotagem.jsp");
					 rd.forward(req, res);
			    }
//Se a Blobkey for nula e o parâmetro "salvar" não for, significa que é para salvar os dados
	    	}catch(NullPointerException e){
	    		String chaveSalvar = req.getParameter("salvar");
	    		if(chaveSalvar!=null){
	    			salvar(req, res);
	        	}
	    		e.printStackTrace();
	    	}
	    }
	}
	protected void doGet(HttpServletRequest req, HttpServletResponse resp)
			throws IOException, ServletException {
		if (user != null) {
			String blobKey = req.getParameter("blob-key");
			String chaveVer = req.getParameter("ver");
			String chaveDeletar = req.getParameter("deletar");
			String chaveEscrever = req.getParameter("escrever");
//Servir os arquivos de erros e o txt original			
			if(blobKey!=null){
				servir(req, resp);
				return;
//Ver as informações no mapa e nas tabelas
			}else if(chaveVer!=null){
				ver(req, resp);
				return;
//Deletar as linhas
			}else if(chaveDeletar!=null){
				deletar(req, resp);
				return;
//Escrever o kml após as correções que se mostrarem necessárias
			}else if(chaveEscrever!=null){
				escreverKml(req, resp);
				return;
			}
		}
	}
	

	// Escreve o arquivo kml na hora em que é solicitado e já com as alterações realizadas no Google Maps
	private void escreverKml(HttpServletRequest req, HttpServletResponse resp) throws IOException {
		// TODO Auto-generated method stub
		String path = "";
//Chaver para recuperar os dados e escrever o kml
		String chaveEscrever = req.getParameter("escrever");
		SortedMap<String,String[]> mapaArquivos = Utilidades.consultarArquivosUsuario(user.toString());
		String[] lista = mapaArquivos.get(chaveEscrever);
		AppEngineFile plotagem = fileService.createNewBlobFile("text/plain", lista[0]+"_plotagem.kml");

		String cabecalho = Utilidades.plotarCabecalho(lista[0]).toString();
		
	    FileWriteChannel writeChannel = fileService.openWriteChannel(plotagem, true);
        PrintWriter out = new PrintWriter(Channels.newWriter(writeChannel, "UTF8"));
        
        out.print(cabecalho);
        out.close();
// Json object para manipular as informações
        JSONObject enderecosEncontrados = new JSONObject("{rows:["+lista[5]+"]}");
        JSONArray ArrayEnderecosEncontrados = enderecosEncontrados.getJSONArray("rows");
        String titulos[] = lista[4].split(",");
        for(int i=0;i<ArrayEnderecosEncontrados.length();i++){
        	JSONObject linhaEndereco = (JSONObject) ArrayEnderecosEncontrados.get(i);
        	JSONArray data = linhaEndereco.getJSONArray("data");
        	String html = Utilidades.plotarHtml(data, titulos);
        	System.out.print(html);
        	String placemark = Utilidades.plotarPlacemark(data, html).toString();
        	System.out.print(placemark);
        	path = plotagem.getFullPath();
        	Utilidades.escreverArquivo(placemark, path);
		}
        path = plotagem.getFullPath();
        plotagem = new AppEngineFile(path);
        writeChannel = fileService.openWriteChannel(plotagem, true);
        writeChannel.write(ByteBuffer.wrap("</Document></kml>".getBytes()));
        writeChannel.closeFinally();
        BlobKey blobKeySaida = fileService.getBlobKey(plotagem);
        BlobInfo info = new BlobInfoFactory().loadBlobInfo(blobKeySaida);
//O nome do arquivo a servir deve estar em ASCII
		String Nome = Normalizer.normalize(info.getFilename(), Normalizer.Form.NFD).replaceAll("[^\\p{ASCII}]", "");
		Nome = MimeUtility.encodeWord(Nome);
		resp.setHeader("content-disposition", "attachment; filename="+Nome);
		blobstoreService.serve(blobKeySaida, resp);
	}
	protected void servir(HttpServletRequest req, HttpServletResponse resp) throws IOException{
// Simplesmente serve os arquivos por meio do BlobstoreService
		BlobKey blobKey = new BlobKey(req.getParameter("blob-key"));
		BlobInfo info = new BlobInfoFactory().loadBlobInfo(blobKey);
		String Nome = Normalizer.normalize(info.getFilename(), Normalizer.Form.NFD).replaceAll("[^\\p{ASCII}]", "");
		Nome = MimeUtility.encodeWord(Nome);

		resp.setHeader("content-disposition", "attachment; filename="+Nome);
		blobstoreService.serve(blobKey, resp);
	}
	
	protected void ver(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//Recupera os dados solicitados e envia à JSP
		RequestDispatcher rd = null;
		String chaveVer = req.getParameter("ver");
		SortedMap<String,String[]> mapaArquivos = Utilidades.consultarArquivosUsuario(user.toString());
		String[] lista = mapaArquivos.get(chaveVer);
		req.setAttribute("titulos", lista[4] );
		req.setAttribute("enderecosEncontrados", lista[5] );
		req.setAttribute("enderecosErros", lista[6] );
		req.setAttribute("chave", chaveVer);
		rd=req.getRequestDispatcher("/plotagem.jsp");
		rd.forward(req, resp);
	}
	
	protected void deletar(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//Apaga as informações do mapa e os arquivos de erros e original
		RequestDispatcher rd = null;
		String chaveDeletar = req.getParameter("deletar");
		Utilidades.deletarDoMapa(user.toString(),chaveDeletar);
		rd=req.getRequestDispatcher("/plotagem.jsp");
		rd.forward(req, resp);
	}

	private void salvar(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		// TODO Auto-generated method stub
// Salva as alterações de localização dos Placemarks
// Novamente, o Json object para manipular as informações
		JSONObject enderecosEncontrados = new JSONObject(req.getParameter("enderecosEncontrados"));
		JSONArray ArrayEnderecosEncontrados = enderecosEncontrados.getJSONArray("rows");
		StringBuffer linhasEnderecosEncontrados = new StringBuffer("");
		for(int i=0;i<ArrayEnderecosEncontrados.length();i++){
			if(i>0){linhasEnderecosEncontrados.append(",\n");}
			linhasEnderecosEncontrados.append(ArrayEnderecosEncontrados.get(i));
		}
		System.out.print(linhasEnderecosEncontrados);
		String chaveSalvar = req.getParameter("salvar");
		SortedMap<String,String[]> mapaArquivos = Utilidades.consultarArquivosUsuario(user.toString());
		String[] lista = mapaArquivos.get(chaveSalvar);
		lista[5] = linhasEnderecosEncontrados.toString();
		Utilidades.adicionarNoMapa(user.toString(), chaveSalvar, lista);
		RequestDispatcher rd = null;
		req.setAttribute("titulos", lista[4] );
		req.setAttribute("enderecosEncontrados", lista[5] );
		req.setAttribute("enderecosErros", lista[6] );
		req.setAttribute("chave", chaveSalvar);
		rd=req.getRequestDispatcher("/plotagem.jsp");
		rd.forward(req, resp);
	}
}
